COMP3161/9164 Concepts of Programming Languages
Term 3, 2024

Code and Notes (Week 5 Tuesday)

Table of Contents

1. The M-Machine and C-Machine

The proof of contextuality of the C-Machine to M-Machine refinement.

The goal is, for instance, to take this step of the M-machine:

Plus (Num v1) (Num v2) |->M Num (v1 + v2)

We want to place that step *in context*:

A (s ≻ Plus (Num v1) (Num v2)) |->M A (s ≻ Num (v1 + v2))

More generally, we want to show:

       e1 |->M e1'
---------------------------
A (s ≻ e1) |->M A (s ≻ e1')

The proof here, if we did it formally, would be by induction on the
stack s, which is just a list.

We set out to prove
∀s. ∀e1 e1'. e1 |->M e1'  -→  A (s ≻ e1) |->M A (s ≻ e1'),
by induction on the length of the stack s.

The base case is where the list is empty (or length 0). In that case,
∀e. A (s ≻ e) ≡ e, and our implication is trivial.

The step case is where the list has one more element than in the IH.
So, let us say our stack has the shape (f ▷ s), one more frame than s,
and our IH applies to s.

For some e1 and e1', we assume e1 |->M e1' and want to show:

A ((f ▷ s) ≻ e1) |->M A ((f ▷ s) ≻ e1')

A is defined by recursion on the stack (good!) and then by case
division on the shape of f, the topmost stack element. Our proof
does the same case division.

So, for instance, in the case where f is "Plus □ e2",

A ((f ▷ s) ≻ e1) = A (((Plus □ e2) ▷ s) ≻ e1)
  ≡ A (s ≻ Plus e1 e2)

and

A ((f ▷ s) ≻ e1') = A (((Plus □ e2) ▷ s) ≻ e1)
  ≡ A (s ≻ Plus e1' e2)

So our goal can be rewritten to:

A (s ≻ Plus e1 e2) |->M A(s ≻ Plus e1' e2)

Our IH applies to this goal shape, and tells us it is true if
we can establish this:

Plus e1 e2 |->M Plus e1' e2

We already have e1 |->M e1', and this is just the left
contextual rule for M-steps of Plus, or alternatively, the
left contextual rule for Plus in our small-step relation for
the semantics of MinHS.

Likewise, in the case where f has the shape Plus v1 □, the
definition of A gives us

A ((f ▷ s) ≻ e1) = A (((Plus v1 □) ▷ s) ≻ e1)
  ≡ A(s ≻ Plus (Num v1) e1)

(Note the detail we are insisting on, when the "hole" is at
the right operand of Plus, we assume that the left operand is
reduced to a value (v1 as opposed to Num v1)).

Again, this case of the proof boils down to an application of
one of the contextual rules from our small-step language definition.

Here's our MinHS implementation with a quick instantiation of this system as code. The final functions evalframe and pushvalframe implement our evaluation judgement (for the "stack ≻ expr" and "stack ≺ val" cases respectively).

double_hs :: Int -> Int
double_hs x = x + x

divBy5 :: Int -> Int
divBy5 x = if x < 5 then 0 else 1 + divBy5 (x - 5)

add :: Int -> Int -> Int
add x y = x + y

data MinHS =
  Num Int
  | Lit Bool
  | If MinHS MinHS MinHS
  | Apply MinHS MinHS
  | Recfun Type Type (MinHS -> MinHS -> MinHS)
  | Plus MinHS MinHS
  | Minus MinHS MinHS
  | Eq MinHS MinHS
  | Times MinHS MinHS
  | LessThan MinHS MinHS
  | Tag String
  | TypeTag Type

data Type = BoolTy | IntTy | FunTy Type Type
  deriving (Eq,Show)
  -- tells Haskell to automatically define a pretty-printer
  -- and equality comparisons for our type

data Value = Bool_Val Bool | Int_Val Int
    | Fun_Val (MinHS -> MinHS -> MinHS)

double_minhs :: MinHS
double_minhs = Recfun IntTy IntTy (\f x -> Plus x x)

divBy5_minhs :: MinHS
divBy5_minhs = Recfun IntTy IntTy (\f x -> If (LessThan x (Num 5))
   (Num 0) (Plus (Num 1) (Apply f (Minus x (Num 5)))))

add_minhs :: MinHS
add_minhs = Recfun IntTy (FunTy IntTy IntTy) (\f x ->
    Recfun IntTy IntTy (\f y -> Plus x y))

-- to print (\f x -> Plus x x), we will invent some
-- special values "f" and "x" and pass them to the function

printer :: MinHS -> String
printer minhs = case minhs of
  Num n -> show n
  Lit b -> show b
  Plus x y -> concat ["(", printer x, " + ", printer y, ")"]
  Minus x y -> concat ["(", printer x, " - ", printer y, ")"]
  Eq x y -> concat ["(", printer x, " == ", printer y, ")"]
  LessThan x y -> concat ["(", printer x, " < ", printer y, ")"]
  Apply x y -> concat ["(", printer x, " ", printer y, ")"]
  If c x y -> concat ["(if ", printer c, " then ", printer x,
        " else ", printer y, ")"]
  Recfun t1 t2 f ->
    let f_nm = "f" in
    let x_nm = "x" in
    concat ["(recfun ", f_nm, " :: (", show t1, " -> ", show t2,
        ") ", x_nm, " = ", printer (f (Tag f_nm) (Tag x_nm))]
  Tag nm -> nm
  TypeTag ty -> ("(TypeTag " ++ show ty ++ ")")
  _ -> error ("MinHS pretty-printer: unimplemented")

-- Skipped for now: fix the pretty-printer to not always use
-- the same name "x". It is pretty broken at the moment.

check :: MinHS -> Type -> Bool
check x ty = (if type_checker x == ty
    then True
    else error ("check: types disagree for " ++
        show (printer x, ty, type_checker x)))

type_checker :: MinHS -> Type
type_checker (Tag nm) = error ("type_checker: Tag should not appear")
type_checker (TypeTag ty) = ty
type_checker (Recfun t1 t2 f) =
    if check (f (TypeTag (FunTy t1 t2)) (TypeTag t1)) t2
    then FunTy t1 t2
    else error ("Recfun: invalid types")
type_checker (Apply f x) = case type_checker f of
    FunTy t1 t2 -> if check x t1 then t2 else (error "Apply: types")
    t -> error ("type_checker: application of " ++ show t)
type_checker (Plus x y) = if check x IntTy && check y IntTy
    then IntTy else error ("Plus: invalid types")
type_checker (Minus x y) = if check x IntTy && check y IntTy
    then IntTy else error ("Minus: invalid types")
type_checker (Eq x y) = if check x IntTy && check y IntTy then BoolTy
    else error ("Plus: invalid types")
type_checker (LessThan x y) = if check x IntTy && check y IntTy
    then BoolTy else error ("Plus: invalid types")
type_checker (If c x y) = if check c BoolTy then
        let t = type_checker x in
        if check y t then t else error ("If: invalid types")
    else error ("If: condition not a boolean")
type_checker (Num n) = IntTy
type_checker (Lit b) = BoolTy
type_checker x = error ("type_checker: unimplemented: " ++ printer x)



-- To type-check Recfun t1 t2 (\f x -> body), we will need
-- (in type-checking the body) just that x has type t2 and
-- f has type (FunTy t1 t2). So we replace f with TypeTag ...


eval :: MinHS -> MinHS
eval e = case e of
  Num _ -> e
  Lit _ -> e
  Recfun _ _ _ -> e
  Apply f x ->
    let f2 = eval f in
    let x2 = eval x in
    case f2 of
      Recfun _ _ body_fn -> eval (body_fn f2 x2)
      exp -> error ("eval: type-incorrect apply of " ++ printer exp)
  _ -> error ("eval: unimplemented: " ++ printer e)


data MinHS_Frame =
  Plus_Frame_1 () MinHS
  | Plus_Frame_2 Value ()
  | If_Frame () MinHS MinHS
  | Apply_Frame_2 () MinHS
  | Apply_Frame_1 Value ()

eval_frame :: MinHS -> [MinHS_Frame] -> Value
eval_frame (Plus e1 e2) stack =
    eval_frame e1 ([Plus_Frame_1 () e2] ++ stack)
eval_frame (Num n) stack = push_val_frame (Int_Val n) stack

push_val_frame :: Value -> [MinHS_Frame] -> Value
push_val_frame v [] = v
push_val_frame v (Plus_Frame_1 () e : stack) =
    eval_frame e (Plus_Frame_2 v () : stack)
push_val_frame (Int_Val i1) (Plus_Frame_2 (Int_Val i2) () : stack) =
    push_val_frame (Int_Val (i1 + i2)) stack


print_val :: Value -> String
print_val (Int_Val i) = show i
print_val (Bool_Val b) = show b

This evaluator works! It can only evaluate additions of numbers, so it is hardly useful so far, but we are making progress.

2024-11-28 Thu 20:06

Announcements RSS